home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume8 / ansitape / part01 next >
Encoding:
Internet Message Format  |  1987-03-02  |  31.3 KB

  1. Subject:  v08i099:  ANSI tape program, Part01/02
  2. Newsgroups: mod.sources
  3. Approved: mirror!rs
  4.  
  5. Submitted by: David S. Hayes <sundc!hqda-ai!merlin>
  6. Mod.sources: Volume 8, Issue 99
  7. Archive-name: ansitape/Part01
  8.  
  9.      This is a program that I've had around for some time, but never
  10. really did anything with before.  I use it for transferring data
  11. between VAX/VMS, IBM MVS, and Unix 4.2BSD.  It works well for me, but
  12. someone out there is likely to find a bug.
  13.  
  14.      In particular, I could not get a full copy of the ANSI magtape
  15. standards, so I did the best I could by testing to see what would
  16. work.  I think this does enough to be useful in most cases, though.
  17.  
  18.      I'm not in the support business, but I would like to hear about
  19. bugs.  As time permits, I'll try to fix them.  Updates will be
  20. published in mod.sources, using 'patch' formats.  I'd like to do the
  21. 'official' patches here, so everyone doesn't end up with custom
  22. (incompatible) changes.
  23.  
  24.      This software was developed on Government time, and is therefore
  25. freely available to the public.  (It's even covered under the Freedom
  26. of Information Act, if anyone ever cared to ask for it.)
  27.  
  28. Hope someone finds this useful.
  29.  
  30.     David S. Hayes, The Merlin of Avalon
  31.     PhoneNet:    (202) 694-6900
  32.     ARPA:        merlin%hqda-ai.uucp@brl-smoke
  33.     UUCP:        ...!seismo!sundc!hqda-ai!merlin
  34.  
  35. #!/bin/sh
  36. # This is a shell archive.  Remove anything before this line, then
  37. # unpack it by saving it in a file and typing "sh file".  (Files
  38. # unpacked will be owned by you and have default permissions.)
  39. #
  40. # This archive contains:
  41. # ansitape.c
  42. echo x - ansitape.c
  43. cat > "ansitape.c" << '//E*O*F ansitape.c//'
  44. /*
  45.  * ANSITAPE.C 
  46.  *
  47.  * This program creates, reads, writes, and takes directory listings of
  48.  * ANSI-formatted tapes. 
  49.  *
  50.  * This program was developed at 
  51.  *
  52.  * HQDA Army Artificial Intelligence Center
  53.  * Pentagon Attn: DACS-DMA
  54.  * Washington, DC  20310-0200 
  55.  *
  56.  * Phone: (202) 694-6900 
  57.  *
  58.  ********************************************
  59.  *
  60.  * THIS PROGRAM IS IN THE PUBLIC DOMAIN 
  61.  *
  62.  ********************************************
  63.  *
  64.  * Modification History:
  65.  *
  66.  * 28 January 1987    David S. Hayes Cleaned up for Usenet,
  67.     and changed switch formats.
  68.   
  69.  * 6 August 1986    David S. Hayes Added -st I/O switch. 
  70.   
  71.  * 28 July 1986        David S. Hayes Changed to stream-style tape
  72.     access, added IBM labels and EBCDIC code conversion. 
  73.   
  74.  * 18 July 1986        David S. Hayes Changed to correct problem with
  75.     files spanning more than one block. 
  76.   
  77.  * 17 June 1986        David S. Hayes Original version. 
  78.  */
  79.  
  80. #define SYSID "UNIXTAPEV2.0"    /* version 2.0 patch level 0 */
  81.  
  82. #include <stdio.h>
  83. #include <strings.h>
  84. #include <ctype.h>
  85. #include <sys/file.h>
  86. #include <sys/types.h>
  87. #include <sys/ioctl.h>
  88. #include <sys/mtio.h>
  89. #include <sys/time.h>
  90. #include <sys/stat.h>
  91.  
  92. #include "ansitape.h"
  93.  
  94. #define DEFAULT_TAPE "/dev/rmt8"
  95. #define IGN ' '            /* empty constant for hdr space fill */
  96.  
  97. /*
  98.  * The largest file that we will try to determine recordsize for.
  99.  * Anything larger than this, we just use recordsize = blocksize. 
  100.  */
  101. #define READMAX 100000
  102.  
  103.  
  104. #define LIST        1
  105. #define EXTRACT        2
  106. #define WRITE        3
  107. #define CREATE        4
  108.  
  109.  
  110. #define makelower(c)    ( isupper(c) ? tolower(c) : c)
  111. #define makeupper(c)    ( islower(c) ? toupper(c) : c)
  112. #define max(a,b)    ( a >= b ? a : b )
  113. #define min(a,b)    ( a < b ? a : b )
  114.  
  115. #define SAME 0
  116.  
  117.  
  118. typedef unsigned char FLAG;    /* boolean values only */
  119. #define TRUE        '\001'
  120. #define FALSE        '\000'
  121.  
  122.  
  123. extern int      errno;
  124.  
  125. char           *upstring();
  126. char           *downstring();
  127. char           *tail();
  128. char           *malloc();
  129. char           *alloca();
  130. FLAG            match();
  131. FLAG            eot();
  132.  
  133.  
  134.  
  135. char           *labels[] = {"", "HDR", "EOV", "EOF", ""};
  136. #define TOP_OF_FILE    1
  137. #define END_OF_TAPE    2
  138. #define END_OF_FILE    3
  139. #define END_OF_SET    4
  140. #define UNKNOWN_LABEL    -1
  141.  
  142.  
  143. struct ansi_vol1 vol1;
  144. struct ansi_hdr1 hdr1;
  145. struct ansi_hdr2 hdr2;
  146. struct ansi_hdr3 hdr3;
  147. struct ansi_hdr4 hdr4;
  148.  
  149. struct tape_control_block {
  150.     /* Stuff pertaining to the tape set. */
  151.     FLAG            ebcdic;
  152.     FLAG            ibm_format;
  153.     int             reel_switch;
  154.     char            set_name[6];
  155.     char            tapesys[13];
  156.     char            owner[14];
  157.     char            vol_access;
  158.     int             ansi_level;
  159.     int             reel_count;
  160.  
  161.     /* Stuff pertaining to an individual volume. */
  162.     char            vol_name[6];
  163.     FLAG            mounted;
  164.     int             file_count;
  165.  
  166.     /* Stuff for an individual file. */
  167.     char            tape_filename1[17];
  168.     char            tape_filename2[63];
  169.     char            unix_filename[80];
  170.     int             blk_size;
  171.     int             rec_size;
  172.     char            rec_format;
  173.     char            car_control;
  174.     int             generation;
  175.     int             genversion;
  176.     char            file_access;
  177.     long            created;
  178.     long            expires;
  179.     int             density;
  180.     int             blk_count;
  181.     int             blk_offset;
  182.  
  183.     /* Stuff to be able to read/write on the tape. */
  184.     int             fd;
  185.     char           *buffer;
  186.     int             char_count;
  187.     int             next;
  188.     FLAG            eof;
  189.     FLAG            open;
  190.     int             rw_mode;
  191. }               tcb;
  192.  
  193.  
  194. int             func;        /* What we're supposed to do */
  195. char           *tapename = DEFAULT_TAPE;
  196. FLAG            verbose = FALSE,/* be wordy */
  197.                 query = FALSE,    /* ask before doing */
  198.                 no_hdr3 = FALSE,/* don't write HDR3 or HDR4 */
  199.                 ibm_format = FALSE,    /* use IBM standard label
  200.                      * format */
  201.                 ebcdic = FALSE,    /* tape should be EBCDIC */
  202.                 flag_bs = FALSE,/* blocksize was given */
  203.                 flag_rs = FALSE,/* recordsize was given */
  204.                 flag_stdio = FALSE;    /* do file i/o using stdin/out */
  205.  
  206. char            main_set_name[6] = "UNIX";    /* file set id */
  207. char            main_vol_name[6] = "UNIX";    /* currently-mounted
  208.                          * volume */
  209. char           *username,
  210.                *progname;
  211. int             main_blk_size = 2048,
  212.                 main_rec_size;
  213. char            main_rec_format = REC_VARIABLE;    /* fixed or variable */
  214. char            main_car_control = CC_IMPLIED;    /* carriage control */
  215.  
  216. char          **main_file_list;
  217. int             main_file_count;
  218.  
  219.  
  220. /*
  221.  * Here's the code. 
  222.  */
  223.  
  224. main(argc, argv)
  225.     int             argc;
  226.     char           *argv[];
  227. {
  228.     int             i;
  229.     char           *getlogin();
  230.  
  231.     setbuf(stderr, NULL);    /* unbuffer error output */
  232.  
  233.     username = getlogin();
  234.     progname = argv[0];
  235.     if (username) {
  236.     downstring(username, main_vol_name, 6);
  237.     downstring(username, main_set_name, 6);
  238.     };
  239.  
  240.     if (argc >= 2) {
  241.     /* Now 1-character option names. */
  242.     for (i = 0; argv[1][i]; i++) {
  243.         if (argv[1][i] == '-')
  244.         continue;    /* minus is optional on first keylist */
  245.         switch (argv[1][i]) {
  246.           case 't':
  247.         func = LIST;
  248.         break;
  249.  
  250.           case 'x':
  251.         func = EXTRACT;
  252.         break;
  253.  
  254.           case 'c':
  255.         func = CREATE;
  256.         break;
  257.  
  258.           case 'r':
  259.         if (func != CREATE)
  260.             func = WRITE;
  261.         break;
  262.  
  263.           case 'v':
  264.         verbose = TRUE;
  265.         break;
  266.  
  267.           case 'q':
  268.         query = TRUE;
  269.         break;
  270.  
  271.         /* Do file I/O to stdio instead. */
  272.           case 'f':
  273.         flag_stdio = TRUE;
  274.         break;
  275.  
  276.         /*
  277.          * Suppress HDR3 headers for systems that can't handle
  278.          * them. 
  279.          */
  280.           case '3':
  281.         no_hdr3 = TRUE;
  282.         break;
  283.  
  284.         /* Tape is to be written in ASCII. */
  285.           case 'a':
  286.         ebcdic = FALSE;
  287.         break;
  288.  
  289. #ifdef EBCDIC
  290.           case 'e':
  291.         ebcdic = TRUE;
  292.         break;
  293.  
  294.           case 'i':
  295.         ibm_format = TRUE;
  296.         ebcdic = TRUE;
  297.         break;
  298. #endif
  299.  
  300.           default:
  301.         fprintf(stderr, "unknown key %c - ignored\n", argv[1][i]);
  302.         };
  303.     };
  304.     };
  305.  
  306.     for (i = 2; i < argc; i++) {
  307.     /* Do 2-character options */
  308.  
  309.     if (strncmp(argv[i], "cc=", 3) == SAME) {
  310.         switch (argv[i][3]) {
  311.           case 'f':
  312.         main_car_control = CC_FORTRAN;
  313.         break;
  314.           case 'i':
  315.         main_car_control = CC_IMPLIED;
  316.         break;
  317.           case 'e':
  318.         main_car_control = CC_EMBEDDED;
  319.         break;
  320.           default:
  321.         fprintf(stderr, "unknown carriage control option %s\n", argv[i]);
  322.         };
  323.         continue;
  324.     };
  325.  
  326.     if (strncmp(argv[i], "rs=", 3) == SAME) {
  327.         if (strcmp(argv[i], "rs=r") == SAME) {
  328.         main_rec_size = 0;
  329.         main_rec_format = REC_VARIABLE;
  330.         } else {
  331.         flag_rs = TRUE;
  332.         sscanf(argv[i], "rs=%d", &main_rec_size);
  333.         main_rec_format = REC_FIXED;
  334.         };
  335.         continue;
  336.     };
  337.  
  338.     if (strncmp(argv[i], "bs=", 3) == SAME) {
  339.         flag_bs = TRUE;
  340.         sscanf(argv[i], "bs=%d", &main_blk_size);
  341.         continue;
  342.     };
  343.  
  344.     if (strncmp(argv[i], "vo=", 3) == SAME) {
  345.         downstring(&argv[i][3], main_set_name, 6);
  346.         downstring(&argv[i][3], main_vol_name, 6);
  347.         continue;
  348.     };
  349.  
  350.     if (strncmp(argv[i], "rf=", 3) == SAME) {
  351.         switch (argv[i][3]) {
  352.           case 'f':
  353.         main_rec_format = REC_FIXED;
  354.         break;
  355.           case 'v':
  356.         main_rec_format = REC_VARIABLE;
  357.         break;
  358.           default:
  359.         fprintf(stderr, "unknown record format %c - ignored\n",
  360.             argv[i][3]);
  361.         };
  362.         continue;
  363.     };
  364.  
  365.     if (strncmp(argv[i], "mt=", 3) == SAME) {
  366.         tapename = &argv[i][3];
  367.         continue;
  368.     };
  369.  
  370.     /* That's not an option, assume it's a file name. */
  371.     break;
  372.     };
  373.  
  374.     /* Figure out where and how many file names we have. */
  375.     main_file_count = argc - i;
  376.     main_file_list = argc ? &argv[i] : (char **) 0;
  377.  
  378.     if (main_rec_size > main_blk_size) {
  379.     if (flag_rs) {
  380.         main_blk_size = main_rec_size;
  381.         fprintf(stderr, "blocksize adjusted up to recordsize (%d)\n",
  382.             main_rec_size);
  383.     } else {
  384.         main_rec_size = main_blk_size;
  385.         fprintf(stderr, "recordsize adjusted down to blocksize (%d)\n",
  386.             main_rec_size);
  387.     };
  388.     };
  389.  
  390.     switch (func) {
  391.       case WRITE:
  392.     tcb.fd = open(tapename, O_RDWR, 0666);
  393.     break;
  394.  
  395.       case CREATE:
  396.     tcb.fd = open(tapename, O_RDWR | O_CREAT | O_TRUNC, 0666);
  397.     break;
  398.  
  399.       default:
  400.     tcb.fd = open(tapename, O_RDONLY, 0666);
  401.     }
  402.  
  403.     if (errno) {
  404.     perror("can't open tape device");
  405.     exit(-1);
  406.     };
  407.  
  408.     switch (func) {
  409.       case LIST:
  410.     list_volume_set();
  411.     break;
  412.  
  413.       case CREATE:
  414.       case WRITE:
  415.     write_volume_set();
  416.     break;
  417.  
  418.       case EXTRACT:
  419.     read_volume_set();
  420.     break;
  421.  
  422.       default:
  423.     fprintf(stderr, "specify one of -t, -x, -c, -r\n");
  424.     exit(-1);
  425.     };
  426.     exit(0);
  427. }
  428.  
  429.  
  430. /* List contents of a full volume set. */
  431.  
  432. list_volume_set()
  433. {
  434.     mount_tape();
  435.     for (; ansi_open(FREAD) == TOP_OF_FILE;) {
  436.     ansi_close();
  437.     list_file();
  438.     };
  439. }
  440.  
  441.  
  442. /* List one file, possibly with maximized verbosity. */
  443.  
  444. list_file()
  445. {
  446.     printf("%-22s", tcb.unix_filename);
  447.  
  448.     if (verbose) {
  449.     printf(" %5d blocks", tcb.blk_count);
  450.     printf(", rec fmt %s %d bytes",
  451.            tcb.rec_format == REC_FIXED ? "fixed" : "var max",
  452.            tcb.rec_size);
  453.     switch (tcb.car_control) {
  454.       case CC_IMPLIED:
  455.         printf(", cc implied");
  456.         break;
  457.       case CC_EMBEDDED:
  458.         printf(", cc embedded");
  459.         break;
  460.       case CC_FORTRAN:
  461.         printf(", cc fortran");
  462.         break;
  463.       default:
  464.         printf(", cc code '%c'", tcb.car_control);
  465.     };
  466.     };
  467.     putchar('\n');
  468. }
  469.  
  470.  
  471.  
  472. /*
  473.  * Write files to a volume set.  Uses the ansi_write buffered output
  474.  * function, which automatically chains to new tapes if it has to. 
  475.  */
  476.  
  477. write_volume_set()
  478. {
  479.     int             j;
  480.  
  481.     /*
  482.      * First, make ready to write the tape.  If this is create, then we
  483.      * must init the tape.  If not, mount the tape, and advance to the
  484.      * logical end-of-volume-set. 
  485.      */
  486.  
  487.     if (func == CREATE) {
  488.     tcb.reel_count = 0;
  489.     strncpy(tcb.vol_name, main_vol_name, 6);
  490.     strncpy(tcb.set_name, main_set_name, 6);
  491.     strncpy(tcb.owner, username, 14);
  492.     init_tape();
  493.     } else {
  494.     mount_tape();
  495.  
  496.     do {
  497.         j = ansi_open(FREAD);
  498.         ansi_close();
  499.     } while (j != END_OF_SET);
  500.  
  501.     tape(MTBSF, 1);        /* back up past last tape mark */
  502.     };
  503.  
  504.     for (j = 0; j < main_file_count; j++)
  505.     write_file(main_file_list[j]);
  506.     tape(MTWEOF, 2);
  507. }
  508.  
  509. /*
  510.  * Write from a stream to a tape.  Lots of special stuff, like record
  511.  * and block sizes, are imported implicitly from static variables that
  512.  * were initialized by command-line options. 
  513.  */
  514.  
  515. write_file(fn)
  516.     char           *fn;
  517. {
  518.     int             stat;
  519.     FILE           *fp;
  520.     struct stat     sb;        /* check on our input file */
  521.     int             rlen;    /* length of a single record */
  522.     FLAG            disk_file;    /* our input is from a rewind-able
  523.                  * source */
  524.  
  525.     if (query) {
  526.     fprintf(stderr, "r %s ? ", fn);
  527.     if (!yes_or_no())
  528.         return END_OF_FILE;
  529.     };
  530.  
  531.     if (flag_stdio)
  532.     fp = stdin;
  533.     else
  534.     fp = fopen(fn, "r");
  535.     if (fp == NULL) {
  536.     fprintf(stderr, "can't read %s", fn);
  537.     perror(" - ignored");
  538.     return END_OF_FILE;
  539.     };
  540.  
  541.     strncpy(tcb.unix_filename, tail(fn), 80);
  542.     if (verbose)
  543.     fprintf(stderr, "r %-17s", tcb.unix_filename);
  544.  
  545.     tcb.ibm_format = ibm_format;
  546.     tcb.ebcdic = ebcdic;
  547.     tcb.rec_format = main_rec_format;
  548.     tcb.car_control = main_car_control;
  549.     tcb.generation = 1;
  550.     tcb.genversion = 0;
  551.     tcb.file_access = NOPROTECT;
  552.     tcb.blk_offset = 0;
  553.     tcb.blk_size = main_blk_size;
  554.     tcb.rec_size = flag_rs ? main_rec_size : main_blk_size;
  555.  
  556.     errno = 0;
  557.     fstat(fileno(fp), &sb);
  558.     disk_file = (errno == 0) && (sb.st_mode & S_IFMT) == S_IFREG;
  559.     tcb.created = sb.st_ctime;
  560.  
  561.     if (!flag_rs && disk_file && main_rec_format == REC_VARIABLE) {
  562.     if (sb.st_size < READMAX || main_rec_size == 0) {
  563.         tcb.rec_size = 0;
  564.         rlen = 4;        /* line length field */
  565.         while (!feof(fp)) {
  566.         if (getc(fp) == '\n') {
  567.             tcb.rec_size = max(tcb.rec_size, rlen);
  568.             rlen = 4;    /* line length field */
  569.         } else
  570.             rlen++;
  571.         };
  572.         rewind(fp);
  573.     };
  574.     };
  575.  
  576.     if (tcb.rec_size > tcb.blk_size) {
  577.     fprintf(stderr, "%s recordsize (%d) is too big for blocksize - ignored\n", fn, tcb.rec_size);
  578.     return END_OF_FILE;
  579.     };
  580.  
  581.     ansi_open(FWRITE);
  582.     do {
  583.     stat = write_record(fp);
  584.     } while (stat != END_OF_FILE);
  585.  
  586.     ansi_close();
  587.     if (!flag_stdio)
  588.     fclose(fp);
  589.     if (verbose)
  590.     fprintf(stderr, "  %8d blocks\n", tcb.blk_count);
  591.     return END_OF_FILE;
  592. }
  593.  
  594.  
  595. /* Write a block of data */
  596.  
  597. write_record(fp)
  598.     FILE           *fp;
  599. {
  600.     char           *linebuf;
  601.     int             rl;
  602.  
  603.     linebuf = alloca(tcb.blk_size);    /* goes away on function exit */
  604.  
  605.     if (tcb.rec_format == REC_FIXED) {
  606.     rl = fread(linebuf, sizeof(char), tcb.rec_size, fp);
  607.     if (rl == 0)
  608.         return END_OF_FILE;
  609.     } else {
  610.     /* format is variable-length records */
  611.     if (fgets(linebuf, tcb.rec_size, fp) == NULL)
  612.         return END_OF_FILE;
  613.     rl = strlen(linebuf);
  614.     };
  615.  
  616.     ansi_write(linebuf, rl);
  617.     return 0;
  618. }
  619.  
  620.  
  621.  
  622. /*
  623.  * Read from a tape.  The var main_file_list is a list of files that we
  624.  * should extract.  We apply filename wildcarding using ? and * to the
  625.  * names.  (These must be quoted to prevent shell interpretation.)  If
  626.  * no filenames are supplied, then we extract everything. 
  627.  */
  628.  
  629. read_volume_set()
  630. {
  631.     int             i;
  632.     FLAG            extract;
  633.  
  634.     for (; ansi_open(FREAD) != END_OF_SET;) {
  635.     extract = main_file_count == 0;
  636.     for (i = 0; i < main_file_count && !extract; i++) {
  637.         extract = (match(tcb.unix_filename, main_file_list[i]));
  638.     };
  639.  
  640.     if (extract) {
  641.         if (query) {
  642.         fprintf(stderr, "x  %s ? ", tcb.unix_filename);
  643.         if (!yes_or_no())
  644.             continue;
  645.         };
  646.         if (verbose)
  647.         fprintf(stderr, "x  %-17s", tcb.unix_filename);
  648.         read_file();
  649.     };
  650.  
  651.     ansi_close();
  652.     scan_labels();
  653.     if (extract && verbose)
  654.         fprintf(stderr, "  %8d blocks\n", tcb.blk_count);
  655.     };
  656. }
  657.  
  658.  
  659. read_file()
  660. {
  661.     int             max_reclen,
  662.                     reclen;
  663.     char           *record;
  664.     FILE           *fp;
  665.  
  666.     record = malloc(max_reclen = tcb.rec_size + 20);
  667.     /* a little extra room */
  668.  
  669.     if (!flag_stdio)
  670.     fp = fopen(tcb.unix_filename, "w");
  671.     else
  672.     fp = stdout;
  673.  
  674.     while ((reclen = ansi_read(record, max_reclen)) != NULL) {
  675.     fwrite(record, sizeof(char), reclen, fp);
  676.     };
  677.     if (!flag_stdio)
  678.     fclose(fp);
  679. }
  680.  
  681.  
  682.  
  683. /***************************************************
  684.  *                                                 *
  685.  *   A N S I   S U P P O R T   R O U T I N E S     *
  686.  *                                                 *
  687.  ***************************************************/
  688.  
  689.  
  690. /* ANSI_OPEN.  Open up a tape file, and prepare to read it. */
  691.  
  692. ansi_open(mode)
  693.     int             mode;
  694. {
  695.     int             err;
  696.  
  697.     err = 0;
  698.     if (!tcb.mounted) {
  699.     if (mode == FREAD)
  700.         mount_tape();
  701.     else
  702.         init_tape();
  703.     };
  704.  
  705.     if (mode == FREAD) {
  706.     /* We are going to read a tape. */
  707.     err = read_labels();
  708.     if (err != TOP_OF_FILE)
  709.         return err;
  710.     scan_labels();
  711.     } else {
  712.     /* We are writing a tape. */
  713.     tcb.reel_switch = FALSE;
  714.     tcb.file_count++;
  715.     tcb.blk_count = 0;
  716.  
  717.     make_labels(TOP_OF_FILE);
  718.     write_labels();
  719.     err = TOP_OF_FILE;
  720.     };
  721.  
  722.     if (err != UNKNOWN_LABEL) {
  723.     tcb.open = TRUE;
  724.     tcb.eof = FALSE;
  725.     tcb.rw_mode = mode;
  726.     tcb.blk_count = 0;
  727.     tcb.next = 0;
  728.     tcb.char_count = 0;
  729.     };
  730.  
  731.     return err;
  732. }
  733.  
  734.  
  735. /*
  736.  * ANSI_READ.  Read in a record from an buffered, open tape file. We
  737.  * assume that nothing perverted has happened, like the recordsize
  738.  * being longer than the buffersize. We do automatic reel switching if
  739.  * needed. Return is number of charcters read, or END_OF_TAPE. 
  740.  */
  741.  
  742. ansi_read(bufp, bufl)
  743.     char            bufp[];
  744. int             bufl;
  745. {
  746.     char            rl[5];
  747.     int             read_min,
  748.                     read_count;
  749.  
  750.     if (tcb.eof || !tcb.open)
  751.     return NULL;
  752.  
  753.     read_min = (tcb.rec_format == REC_VARIABLE) ? 4
  754.     : tcb.rec_size;
  755.  
  756.     if (tcb.next + read_min > tcb.char_count) {
  757.     while (tcb.next < tcb.char_count)
  758.         ansi_getc();
  759.     };
  760.  
  761.     if (tcb.rec_format == REC_VARIABLE) {
  762.     do {
  763.         rl[0] = ansi_getc();
  764.     } while (!isdigit(rl[0]) && !tcb.eof);
  765.     rl[1] = ansi_getc();
  766.     rl[2] = ansi_getc();
  767.     rl[3] = ansi_getc();
  768.     rl[4] = '\0';
  769.     read_min = atoi(rl) - 4;
  770.     };
  771.  
  772.     if (tcb.eof)
  773.     return NULL;
  774.  
  775.     read_count = 0;
  776.     for (; bufl && read_count < read_min && !tcb.eof;) {
  777.     *(bufp++) = ansi_getc();
  778.     bufl--, read_count++;
  779.     };
  780.  
  781.     if (bufl && tcb.car_control == CC_IMPLIED) {
  782.     *(bufp++) = '\n';
  783.     bufl--, read_count++;
  784.     };
  785.     if (bufl)
  786.     *bufp = '\0';
  787.  
  788.     return read_count;
  789. }
  790.  
  791.  
  792. /* Read a single character from the tape buffer. */
  793.  
  794. ansi_getc()
  795. {
  796.     int             err;
  797.  
  798. try:
  799.     if (tcb.eof)
  800.     return END_OF_FILE;
  801.  
  802.     if (tcb.buffer && tcb.open) {
  803.     if (tcb.char_count <= tcb.next) {
  804.         tcb.next = 0;
  805.         tcb.char_count = read_tape(tcb.buffer, tcb.blk_size);
  806.         if (tcb.char_count == 0)
  807.         goto do_eof;
  808.     };
  809.     return tcb.buffer[tcb.next++];
  810.     };
  811.  
  812.     if (!tcb.buffer) {
  813.     tcb.buffer = malloc(tcb.blk_size);
  814.     tcb.next = 0;
  815.     goto try;
  816.     };
  817.  
  818. do_eof:
  819.     tcb.eof = TRUE;
  820.     tcb.open = FALSE;
  821.     err = read_labels();
  822.     if (err == END_OF_TAPE) {
  823.     next_volume();
  824.     tcb.eof = FALSE;
  825.     tcb.open = TRUE;
  826.     };
  827.     goto try;
  828. }
  829.  
  830.  
  831. /*
  832.  * ANSI_WRITE.  Write a record, given a pointer and a length. We do all
  833.  * the formatting necessary.  Flush the tape buffer if we run off the
  834.  * end, and start a new one. 
  835.  */
  836.  
  837. ansi_write(rec, len)
  838.     char            rec[];
  839. int             len;
  840. {
  841.     int             overhead = 0;
  842.     char            scratch[10];
  843.  
  844.     if (!tcb.buffer) {
  845.     tcb.buffer = malloc(tcb.blk_size);
  846.     tcb.next = 0;
  847.     tcb.char_count = tcb.blk_size;
  848.     fill(tcb.buffer, tcb.blk_size, '^');
  849.     };
  850.  
  851.     if (tcb.car_control == CC_IMPLIED && len > 0 && iscntrl(rec[len - 1]))
  852.     rec[--len] = '\0';
  853.  
  854.     /* Check to see if we will run off the end of the block. */
  855.     overhead = (tcb.rec_format == REC_VARIABLE) ? 4 : 0;
  856.     if (len + overhead + tcb.next > tcb.blk_size) {
  857.  
  858.     /* Write the old tape block. */
  859.     write_tape(tcb.buffer, tcb.blk_size);
  860.     tcb.blk_count++;
  861.  
  862.     /* Check for physical end of tape. */
  863.     if (eot()) {
  864.         make_labels(END_OF_TAPE);
  865.         write_labels();
  866.         next_volume();
  867.     };
  868.  
  869.     /* Initialize a new tape block. */
  870.     fill(tcb.buffer, tcb.blk_size, '^');
  871.     tcb.next = tcb.blk_offset;
  872.     };
  873.  
  874.     if (tcb.rec_format == REC_VARIABLE) {
  875.     sprintf(scratch, "%04d", len + 4);
  876.     strncpy(&tcb.buffer[tcb.next], scratch, 4);
  877.     tcb.next += 4;
  878.     };
  879.  
  880.     bcopy(rec, &tcb.buffer[tcb.next], len);
  881.     tcb.next += len;
  882.  
  883.     return 0;
  884. }
  885.  
  886.  
  887. /*
  888.  * ANSI_CLOSE.  Close out a file.  We may not have read all the data in
  889.  * the file yet.  If that's so, we skip over the intervening data.
  890.  * Remember that we may have to switch reels. 
  891.  */
  892.  
  893. ansi_close()
  894. {
  895.     int             err;
  896.  
  897.     if (!tcb.open)
  898.     return 0;
  899.  
  900.     if (tcb.rw_mode == FWRITE) {
  901.     if (tcb.next > tcb.blk_offset) {
  902.         write_tape(tcb.buffer, tcb.next);
  903.         tcb.next = tcb.blk_offset;
  904.         tcb.blk_count++;
  905.     };
  906.     tape(MTWEOF, 1);
  907.     make_labels(END_OF_FILE);
  908.     write_labels();
  909.     goto closed;
  910.     };
  911.  
  912.     for (;;) {
  913.     if (!tcb.eof)
  914.         skip_past_marks(1);
  915.     err = read_labels();
  916.     if (err == END_OF_FILE)
  917.         break;
  918.     next_volume();
  919.     tcb.eof = 0;
  920.     };
  921.     scan_labels();
  922.  
  923. closed:
  924.     tcb.eof = tcb.open = FALSE;
  925.     if (tcb.buffer)
  926.     free(tcb.buffer);
  927.     tcb.buffer = 0;
  928.     tcb.char_count = tcb.next = 0;
  929.     return 0;
  930. }
  931.  
  932.  
  933.  
  934. /*
  935.  * Support routines. 
  936.  */
  937.  
  938.  
  939. next_volume()
  940. {
  941.     tape(MTOFFL, 1);
  942.     fprintf(stderr, "Mount continuation tape and push return.  ");
  943.     yes_or_no();
  944.     if (func == LIST || func == EXTRACT) {
  945.     mount_tape();
  946.     read_labels();        /* skip the file continuation HDRn
  947.                  * records */
  948.     } else {
  949.     init_tape();
  950.     ansi_open(tcb.rw_mode);
  951.     };
  952. }
  953.  
  954.  
  955. /*
  956.  * Write a volume header record.  We only write VOL1, since VAX/VMS
  957.  * does not use the VOL2 record. 
  958.  */
  959.  
  960. init_tape()
  961. {
  962.     char            scratch[90];
  963.  
  964.     tcb.ebcdic = ebcdic;
  965.     tcb.ibm_format = ibm_format;
  966.     tcb.ansi_level = 3;
  967.     strncpy(tcb.tapesys, SYSID, 13);
  968.  
  969.     fill(scratch, 90, ' ');
  970.     sprintf(scratch, "VOL1%-6.6s%c",
  971.         tcb.vol_name,    /* volume name */
  972.         NOPROTECT        /* access protection */
  973.     );
  974.  
  975.     if (tcb.ibm_format)
  976.     sprintf(&scratch[41], "%-10.10s%29c",
  977.         tcb.owner,    /* owner of volume set */
  978.         IGN        /* ignored 3 and ansi_level */
  979.         );
  980.     else
  981.     sprintf(&scratch[37], "%-14.14s%28c%1d",
  982.         tcb.owner,    /* owner of volume set */
  983.         IGN,        /* ignored 3 */
  984.         tcb.ansi_level    /* ansi label standard level */
  985.         );
  986.  
  987.     fillnull(scratch, ' ', sizeof(vol1));
  988.     upstring(scratch, (char *) &vol1, sizeof(vol1));
  989.     write_tape((char *) &vol1, sizeof(vol1));
  990.  
  991.     tcb.reel_count++;
  992.     tcb.file_count = 0;
  993.     tcb.mounted = TRUE;
  994.  
  995.     if (verbose)
  996.     fprintf(stderr, "volume %-6s initialized\n", tcb.vol_name);
  997. }
  998.  
  999.  
  1000. /* Read a volume header.  Save the volume name for later. */
  1001.  
  1002. mount_tape()
  1003. {
  1004.     tcb.ebcdic = ebcdic;
  1005.     tcb.ibm_format = ibm_format;
  1006.  
  1007.     read_tape((char *) &vol1, sizeof(vol1));
  1008.     downstring((char *) &vol1, (char *) &vol1, sizeof(vol1));
  1009.     if (strncmp(vol1.header, "vol1", 4) != 0) {
  1010.     fprintf(stderr, "no VOL1 header record\n");
  1011.     exit(-1);
  1012.     };
  1013.  
  1014.     tcb.mounted = TRUE;
  1015.     sscanf((char *) &vol1, "vol1%6c%c",
  1016.        tcb.vol_name,
  1017.        &tcb.vol_access
  1018.     );
  1019.  
  1020.     if (tcb.ibm_format)
  1021.     sscanf(&vol1.owner[4], "%10c", tcb.owner);
  1022.     else
  1023.     sscanf(vol1.owner, "%14c%*28c%1d",
  1024.            tcb.owner,
  1025.            &tcb.ansi_level
  1026.         );
  1027.  
  1028.     if (verbose)
  1029.     fprintf(stderr, "volume %-6s mounted\n", tcb.vol_name);
  1030. }
  1031.  
  1032.  
  1033. /*
  1034.  * Extract the information from a set of headers fetched by
  1035.  * read_labels(). 
  1036.  */
  1037.  
  1038. scan_labels()
  1039. {
  1040.     sscanf((char *) &hdr4, "%*3c4%63c", tcb.tape_filename2);
  1041.  
  1042.     sscanf((char *) &hdr1, "%*3c1%17c%6c%4d%4d%4d%2d",
  1043.        tcb.tape_filename1,
  1044.        tcb.set_name,
  1045.        &tcb.reel_count,
  1046.        &tcb.file_count,
  1047.        &tcb.generation,
  1048.        &tcb.genversion);
  1049.     sscanf(hdr1.created, " %5d %5d%c%6d%17c",
  1050.        &tcb.created,
  1051.        &tcb.expires,
  1052.        &tcb.file_access,
  1053.        &tcb.blk_count,
  1054.        tcb.tapesys);
  1055.  
  1056.     sscanf((char *) &hdr2, "%*3c2%c%5d%5d%1d%1d%*19c%c%*15c%2d",
  1057.        &tcb.rec_format,
  1058.        &tcb.blk_size,
  1059.        &tcb.rec_size,
  1060.        &tcb.density,
  1061.        &tcb.reel_switch,
  1062.        &tcb.car_control,
  1063.        &tcb.blk_offset);
  1064.  
  1065.     if (tcb.ibm_format && hdr2.recfmt == IBM_VARIABLE)
  1066.     tcb.rec_format = REC_VARIABLE;
  1067.  
  1068.     bzero(tcb.unix_filename, 80);
  1069.     sprintf(tcb.unix_filename, "%.17s%.63s",
  1070.         tcb.tape_filename1,
  1071.         tcb.tape_filename2);
  1072.  
  1073.     sscanf(tcb.unix_filename, "%80s", tcb.unix_filename);
  1074.  
  1075.     return 0;
  1076. }
  1077.  
  1078.  
  1079. /*
  1080.  * Write the header for a file whose particulars are in our global
  1081.  * static variables. 
  1082.  */
  1083.  
  1084. make_labels(type)
  1085.     int             type;
  1086. {
  1087.     char            scratch[90];
  1088.     int             namelen;
  1089.  
  1090.     /*
  1091.      * Split up unix_filename into two parts. First is rightmost 17
  1092.      * characters of name, second is other characters, up to 63. 
  1093.      */
  1094.  
  1095.     namelen = strlen(tcb.unix_filename);
  1096.     namelen = min(17, namelen);
  1097.     strncpy(tcb.tape_filename1, tcb.unix_filename, 17);
  1098.     strncpy(tcb.tape_filename2, &tcb.unix_filename[namelen], 63);
  1099.  
  1100.     sprintf(scratch, "%3s1%-17.17s%-6.6s%04d%04d%04d%02d",
  1101.         labels[type],
  1102.         tcb.tape_filename1,
  1103.         tcb.set_name,
  1104.         tcb.reel_count,
  1105.         tcb.file_count,
  1106.         tcb.generation,
  1107.         tcb.genversion);
  1108.     sprintf(&scratch[41], " %05d %05d%1c%06d%-13.13s%7c",
  1109.         ansi_date(tcb.created),
  1110.         ansi_date(tcb.expires),
  1111.         tcb.file_access,
  1112.         tcb.blk_count,
  1113.         tcb.tapesys,
  1114.         IGN);
  1115.     upstring(scratch, (char *) &hdr1, sizeof(hdr1));
  1116.  
  1117.     sprintf(scratch, "%3s2%c%05d%05d%1d%1d%-8.8s/%-8.8s  %1c B%11c%02d%28c",
  1118.         labels[type],
  1119.         tcb.rec_format,
  1120.         tcb.blk_size,
  1121.         tcb.rec_size,
  1122.         tcb.density,
  1123.         tcb.reel_switch,
  1124.         username, progname,    /* job name & job step */
  1125.         tcb.car_control,
  1126.         IGN,
  1127.         tcb.blk_offset,
  1128.         IGN);
  1129.     upstring(scratch, (char *) &hdr2, sizeof(hdr2));
  1130.  
  1131.     /* IBM uses different code for variable-length records. */
  1132.     if (tcb.ibm_format && tcb.rec_format == REC_VARIABLE)
  1133.     hdr2.recfmt = IBM_VARIABLE;
  1134.  
  1135.     if (!tcb.ibm_format && !no_hdr3) {
  1136.     sprintf(scratch, "%3s3%76c",
  1137.         labels[type],
  1138.         IGN);
  1139.     upstring(scratch, (char *) &hdr3, sizeof(hdr3));
  1140.     sprintf(scratch, "%3s4%-63.63s00%11c",
  1141.         labels[type],
  1142.         tcb.tape_filename2,
  1143.         IGN);
  1144.     upstring(scratch, (char *) &hdr4, sizeof(hdr4));
  1145.     };
  1146. }
  1147.  
  1148.  
  1149. /*
  1150.  * Write out tape headers that have already been made by make_labels(). 
  1151.  */
  1152.  
  1153. write_labels()
  1154. {
  1155.     upstring((char *) &hdr1, (char *) &hdr1, sizeof(hdr1));
  1156.     upstring((char *) &hdr2, (char *) &hdr2, sizeof(hdr2));
  1157.     upstring((char *) &hdr3, (char *) &hdr3, sizeof(hdr3));
  1158.     upstring((char *) &hdr4, (char *) &hdr4, sizeof(hdr4));
  1159.  
  1160.     write_tape((char *) &hdr1, sizeof(hdr1));
  1161.     write_tape((char *) &hdr2, sizeof(hdr2));
  1162.  
  1163.     if (!tcb.ibm_format && tcb.ansi_level >= 3 && !no_hdr3) {
  1164.     write_tape((char *) &hdr3, sizeof(hdr3));
  1165.     write_tape((char *) &hdr4, sizeof(hdr4));
  1166.     };
  1167.  
  1168.     tape(MTWEOF, 1);
  1169.     return 0;
  1170. }
  1171.  
  1172.  
  1173. /* Read a tape header.  Set it up for scan_labels(). */
  1174.  
  1175. read_labels()
  1176. {
  1177.     int             l;
  1178.     int             label_type;
  1179.     struct {
  1180.     char            header[3];
  1181.     char            seq;
  1182.     char            data[76];
  1183.     }               inhdr;
  1184.  
  1185.     fill((char *) &hdr1, sizeof(hdr1), ' ');
  1186.     fill((char *) &hdr2, sizeof(hdr2), ' ');
  1187.     fill((char *) &hdr3, sizeof(hdr3), ' ');
  1188.     fill((char *) &hdr4, sizeof(hdr4), ' ');
  1189.  
  1190.     l = read_tape((char *) &inhdr, sizeof(inhdr));
  1191.     if (l == 0)
  1192.     return END_OF_SET;
  1193.  
  1194.     for (label_type = 1; label_type <= 4; label_type++)
  1195.     if (strncmp(inhdr.header, labels[label_type], 3) == 0)
  1196.         break;
  1197.  
  1198.     if (label_type > 4)
  1199.     label_type = UNKNOWN_LABEL;
  1200.  
  1201.     for (; l; l = read_tape((char *) &inhdr, sizeof(inhdr))) {
  1202.     if (label_type == UNKNOWN_LABEL || strncmp(inhdr.header, labels[label_type], 3) != 0)
  1203.         continue;
  1204.     switch (inhdr.seq) {
  1205.       case '1':
  1206.         downstring((char *) &inhdr, (char *) &hdr1, sizeof(hdr1));
  1207.         break;
  1208.       case '2':
  1209.         downstring((char *) &inhdr, (char *) &hdr2, sizeof(hdr2));
  1210.         break;
  1211.       case '3':
  1212.         downstring((char *) &inhdr, (char *) &hdr3, sizeof(hdr3));
  1213.         break;
  1214.       case '4':
  1215.         downstring((char *) &inhdr, (char *) &hdr4, sizeof(hdr4));
  1216.     };
  1217.     };
  1218.  
  1219.     return label_type;
  1220. }
  1221.  
  1222.  
  1223. /*
  1224.  * Return an integer with an ANSI date for the time of creation
  1225.  * (actually, last modification) of a file. 
  1226.  */
  1227.  
  1228. ansi_date(mtime)
  1229.     long            mtime;
  1230. {
  1231.     struct tm      *tm,
  1232.                    *gmtime();
  1233.  
  1234.     if (mtime == 0)
  1235.     mtime = time((long *) 0);
  1236.     tm = gmtime(&mtime);
  1237.     return 1000 * tm->tm_year + tm->tm_yday;
  1238. }
  1239.  
  1240. /*
  1241.  * Write a block of data to the tape drive.  If the tape should be
  1242.  * operated as an EBCDIC device, do the conversion. 
  1243.  */
  1244.  
  1245. write_tape(buffer, buflen)
  1246.     char           *buffer;
  1247.     int             buflen;
  1248. {
  1249.     int             err;
  1250.  
  1251. #ifdef EBCDIC
  1252.     if (tcb.ebcdic)
  1253.     to_ebcdic(buffer, buffer, buflen);
  1254. #endif
  1255.  
  1256.     err = write(tcb.fd, buffer, buflen);
  1257.     return err;
  1258. }
  1259.  
  1260.  
  1261. /*
  1262.  * Read a block from the tape drive.  If the tape should be operated as
  1263.  * an EBCDIC device, do the conversion. 
  1264.  */
  1265.  
  1266. read_tape(buffer, buflen)
  1267.     char           *buffer;
  1268.     int             buflen;
  1269. {
  1270.     int             inlen;
  1271.  
  1272.     inlen = read(tcb.fd, buffer, buflen);
  1273. #ifdef EBCDIC
  1274.     if (tcb.ebcdic)
  1275.     to_ascii(buffer, buffer, inlen);
  1276. #endif
  1277.  
  1278.     return inlen;
  1279. }
  1280.  
  1281.  
  1282. /*
  1283.  * Skip past some number of files on a tape drive. This isn't really
  1284.  * needed on anything but streaming drives. 
  1285.  */
  1286.  
  1287. skip_past_marks(count)
  1288.     int             count;
  1289. {
  1290.     int             i;
  1291.     static char     ignore[20];
  1292.  
  1293.     if (count > 1) {
  1294.     tape(MTFSF, count);
  1295.     return;
  1296.     };
  1297.  
  1298.     for (i = 0; i < 6; i++) {
  1299.     if (read(tcb.fd, ignore, 20) == 0)
  1300.         return;
  1301.     };
  1302.  
  1303.     tape(MTFSF, 1);
  1304. }
  1305.  
  1306.  
  1307. /* Do a special operation on a tape drive. */
  1308.  
  1309. tape(op, count)
  1310.     int             op,
  1311.                     count;
  1312. {
  1313.     struct mtop     mt;
  1314.  
  1315.     mt.mt_op = op;
  1316.     mt.mt_count = count;
  1317.     ioctl(tcb.fd, MTIOCTOP, &mt);
  1318. }
  1319.  
  1320.  
  1321. /* Find the last pathname component of a pathname. */
  1322.  
  1323. char           *
  1324. tail(path)
  1325.     char            path[];
  1326. {
  1327.     int             ix = strlen(path);
  1328.  
  1329.     ix = strlen(path) - 1;
  1330.     for (; ix >= 0; --ix) {
  1331.     if (path[ix] == '/')
  1332.         return &path[++ix];
  1333.     };
  1334.  
  1335.     return path;
  1336. }
  1337.  
  1338.  
  1339. /*
  1340.  * Match a string (s) against a wildcard key.  Returns 1 if they match,
  1341.  * or 0 if they don't. 
  1342.  */
  1343.  
  1344. FLAG
  1345. match(s, key)
  1346.     char           *s,
  1347.                    *key;
  1348. {
  1349.     if (*s == '\0') {
  1350.     switch (*key) {
  1351.       case '\0':
  1352.         return TRUE;
  1353.  
  1354.       case '*':
  1355.         return match(s, ++key);
  1356.  
  1357.       default:
  1358.         return FALSE;
  1359.     };
  1360.     };
  1361.  
  1362.     if (*key == '?')
  1363.     return match(++s, ++key);
  1364.  
  1365.     if (*key == '*')
  1366.     return match(s, (key + 1)) || match((s + 1), key);
  1367.  
  1368.     if (*s == *key)
  1369.     return match(++s, ++key);
  1370.  
  1371.     return FALSE;
  1372. }
  1373.  
  1374.  
  1375. /*
  1376.  * Determine if we are off the physical end of the tape.  For now, just
  1377.  * say we haven't got there yet.  There is no device-independent way to
  1378.  * know when we hit EOT, so if we do this for real, we have a program
  1379.  * that requires a pre-programmed knowledge of the tape controller in
  1380.  * use. 
  1381.  */
  1382.  
  1383. FLAG
  1384. eot()
  1385. {
  1386.     return FALSE;
  1387. }
  1388.  
  1389.  
  1390. /*
  1391.  * Ask the user something, and return ok if we get anything starting
  1392.  * with the letter 'y'. 
  1393.  */
  1394.  
  1395. yes_or_no()
  1396. {
  1397.     char            buf[256];
  1398.     static FILE    *user = NULL;
  1399.  
  1400.     if (user == NULL) {
  1401.     if (flag_stdio)
  1402.         user = fopen("/dev/tty", "r");
  1403.     else
  1404.         user = stdin;
  1405.     };
  1406.  
  1407.     fgets(buf, 256, user);
  1408.     return makelower(buf[0]) == 'y';
  1409. }
  1410.  
  1411.  
  1412. /* Convert a string to upper-case, for at most n characters */
  1413.  
  1414. char           *
  1415. upstring(from, to, len)
  1416.     char           *from,
  1417.                    *to;
  1418.     int             len;
  1419. {
  1420.     char           *to1 = to;
  1421.  
  1422.     for (; len--;) {
  1423.     *to = makeupper(*from);
  1424.     if (*from == '\0')
  1425.         break;
  1426.     to++, from++;
  1427.     };
  1428.  
  1429.     return to1;
  1430. }
  1431.  
  1432. char           *
  1433. downstring(from, to, len)
  1434.     char           *from,
  1435.                    *to;
  1436.     int             len;
  1437. {
  1438.     char           *to1 = to;
  1439.  
  1440.     for (; len--;) {
  1441.     *to = makelower(*from);
  1442.     if (*from == '\0')
  1443.         break;
  1444.     to++, from++;
  1445.     };
  1446.  
  1447.     return to1;
  1448. }
  1449.  
  1450.  
  1451. /* Fill an area with some character. */
  1452.  
  1453. fill(area, size, c)
  1454.     char           *area;
  1455.     int             size;
  1456.     char            c;
  1457. {
  1458.     for (; size--;)
  1459.     *(area++) = c;
  1460. }
  1461.  
  1462.  
  1463. /* Fill in nulls left by sprintf. */
  1464.  
  1465. fillnull(area, fill, len)
  1466.     char           *area,
  1467.                     fill;
  1468.     int             len;
  1469. {
  1470.     for (; len--; area++)
  1471.     if (*area == '\0')
  1472.         *area = fill;
  1473. }
  1474. //E*O*F ansitape.c//
  1475.  
  1476. echo Possible errors detected by \'wc\' [hopefully none]:
  1477. temp=/tmp/shar$$
  1478. trap "rm -f $temp; exit" 0 1 2 3 15
  1479. cat > $temp <<\!!!
  1480.     1430    3772   30164 ansitape.c
  1481. !!!
  1482. wc  ansitape.c | sed 's=[^ ]*/==' | diff -b $temp -
  1483. exit 0
  1484.